﻿//MarketDataStorage.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Algo.Storages
{
	using System;
	using System.Collections;
	using System.Collections.Generic;
	using System.IO;
	using System.Linq;
	using System.Reflection;

	using Ecng.Collections;
	using Ecng.Common;
	using Ecng.Reflection;
	using Ecng.Serialization;

	using StockSharp.BusinessEntities;
	using StockSharp.Logging;

	static class MarketDataVersions
	{
		public static readonly Version Version30 = new Version(3, 0);
		public static readonly Version Version31 = new Version(3, 1);
		public static readonly Version Version33 = new Version(3, 3);
		public static readonly Version Version34 = new Version(3, 4);
		public static readonly Version Version40 = new Version(4, 0);
		public static readonly Version Version41 = new Version(4, 1);
		public static readonly Version Version42 = new Version(4, 2);
		public static readonly Version Version43 = new Version(4, 3);
	}

	abstract class MetaInfo<TMetaInfo> : Cloneable<TMetaInfo>, IMarketDataStorageDayMetaInfo
		where TMetaInfo : MetaInfo<TMetaInfo>
	{
		protected MetaInfo(DateTime date)
		{
			Date = date;
			FirstTime = DateTime.MinValue;
			TimeZoneOffset = LoggingHelper.TimeZoneOffset;
		}

		public DateTime Date { get; private set; }
		public int Count { get; set; }
		public decimal MinStepSize { get; set; }
		public Version Version { get; set; }
		public DateTime FirstTime { get; set; }
		public DateTime LastTime { get; set; }
		public TimeSpan TimeZoneOffset { get; set; }

		// сериализация и десериализация их полей сделана в дочерних классах
		public decimal FirstPrice { get; set; }
		public decimal LastPrice { get; set; }
		public decimal FirstNonSystemPrice { get; set; }
		public decimal PrevNonSystemPrice { get; set; }

		public bool IsEmpty()
		{
			return Count == 0;
		}

		public virtual void Write(Stream s)
		{
			s.WriteByte((byte)Version.Major);
			s.WriteByte((byte)Version.Minor);
			s.Write(Count);
			s.Write(MinStepSize);

			if (Version < MarketDataVersions.Version40)
				s.Write(0m); // ранее был MinStepPrice

			s.Write(FirstTime);
			s.Write(LastTime);

			if (Version >= MarketDataVersions.Version40)
			{
				s.Write(TimeZoneOffset);

				// размер под дополнительную информацию.
				// пока этой информации нет.
				s.Write((short)0);
			}
		}

		public virtual void Read(Stream s)
		{
			Version = new Version(s.ReadByte(), s.ReadByte());
			Count = s.Read<int>();
			MinStepSize = s.Read<decimal>();

			if (Version < MarketDataVersions.Version40)
				s.Read<decimal>(); // ранее был MinStepPrice

			FirstTime = s.Read<DateTime>();
			LastTime = s.Read<DateTime>();

			if (Version >= MarketDataVersions.Version40)
			{
				TimeZoneOffset = s.Read<TimeSpan>();

				// пропускаем блок данных, выделенных под дополнительную информацию
				var extInfoSize = s.Read<short>();

				// здесь можно будет читать доп информацию из потока

				s.Position += extInfoSize;
			}
		}

		public override TMetaInfo Clone()
		{
			var copy = typeof(TMetaInfo).CreateInstance<TMetaInfo>(Date);
			copy.CopyFrom((TMetaInfo)this);
			return copy;
		}

		protected virtual void CopyFrom(TMetaInfo src)
		{
			Version = src.Version;
			Count = src.Count;
			MinStepSize = src.MinStepSize;
			//MinStepPrice = src.MinStepPrice;
			FirstTime = src.FirstTime;
			LastTime = src.LastTime;
			TimeZoneOffset = src.TimeZoneOffset;
		}
	}

	interface IMarketDataStorageInfo<in TData>
	{
		Func<TData, DateTime> GetTime { get; }
	}

	[Obfuscation(Feature = "Apply to member * when abstract: renaming", Exclude = true)]
	abstract class MarketDataStorage<TData, TId, TMetaInfo> : IMarketDataStorage<TData>, IMarketDataStorageInfo<TData>
		where TMetaInfo : MetaInfo<TMetaInfo>
	{
		private readonly Func<TData, DateTime> _getTime;
		private readonly Func<TData, Security> _getSecurity;
		private readonly Func<TData, TId> _getId;
		private readonly object _syncRoot = new object();

		protected MarketDataStorage(Security security, object arg, Func<TData, DateTime> getTime, Func<TData, Security> getSecurity, Func<TData, TId> getId, IMarketDataSerializer<TData, TMetaInfo> serializer, IMarketDataDrive drive)
		{
			if (security == null)
				throw new ArgumentNullException("security");

			if (security.Id.IsEmpty())
				throw new ArgumentException("Идентификатор инструмента пустой.", "security");

			if (getTime == null)
				throw new ArgumentNullException("getTime");

			if (getSecurity == null)
				throw new ArgumentNullException("getSecurity");

			if (getId == null)
				throw new ArgumentNullException("getId");

			if (serializer == null)
				throw new ArgumentNullException("serializer");

			if (drive == null)
				throw new ArgumentNullException("drive");

			Security = security;
			Version = MarketDataVersions.Version40;

			AppendOnlyNew = true;

			_getTime = getTime;
			_getSecurity = getSecurity;
			_getId = getId;
			Drive = drive;
			Serializer = serializer;
			_arg = arg;
		}

		IEnumerable<DateTime> IMarketDataStorage.Dates
		{
			get { return this.GetStorageDrive().Dates; }
		}

		Type IMarketDataStorage.DataType
		{
			get { return typeof(TData); }
		}

		public Security Security { get; private set; }

		private readonly object _arg;

		object IMarketDataStorage.Arg
		{
			get { return _arg; }
		}

		public bool AppendOnlyNew { get; set; }

		protected Version Version { get; set; }

		public IMarketDataSerializer<TData, TMetaInfo> Serializer { get; private set; }
		public IMarketDataDrive Drive { get; private set; }

		private DateTime GetTruncatedTime(TData data)
		{
			return _getTime(data).Truncate();
		}

		public void Save(IEnumerable<TData> data)
		{
			if (data == null)
				throw new ArgumentNullException("data");

			foreach (var group in data.GroupBy(d =>
			{
				var security = _getSecurity(d).CheckMinStepSize();

				if (security != Security)
					throw new ArgumentException("Инструмент для {0} равен {1}, а должен быть {2}.".Put(typeof(TData).Name, security.Id, Security.Id));

				return _getTime(d).Date;
			}))
			{
				Save(group.Key, group.OrderBy(_getTime).ToArray(), false);
			}
		}

		private void Save(DateTime date, TData[] data, bool isOverride)
		{
			if (data == null)
				throw new ArgumentNullException("data");

			lock (_syncRoot)
			{
				var drive = this.GetStorageDrive();

				if (isOverride)
				{
					if (data.IsEmpty())
					{
						drive.Delete(date);
						return;
					}
				}

				TMetaInfo metaInfo;
				byte[] dayData;

				GetData(date, out metaInfo, out dayData);

				if (isOverride || metaInfo == null)
					metaInfo = CreateMetaInfo(date);

				if (metaInfo.Count == 0)
				{
					metaInfo.MinStepSize = Security.MinStepSize;
					metaInfo.LastTime = metaInfo.FirstTime = GetTruncatedTime(data.First());
					metaInfo.Version = Version;
				}
				else
				{
					if (NeedUpgrade(metaInfo))
					{
						data = Load(date).Concat(data).ToArray();
						isOverride = true;

						var time = GetTruncatedTime(data.First());

						metaInfo = CreateMetaInfo(date);
						metaInfo.MinStepSize = Security.MinStepSize;
						metaInfo.Version = Version;
						metaInfo.LastTime = time;
						metaInfo.FirstTime = time;
					}
					else if (AppendOnlyNew)
					{
						data = FilterNewData(data, metaInfo).ToArray();

						if (data.IsEmpty())
							return;
					}
				}

				var newDayData = Serializer.Serialize(data, metaInfo);

				if (isOverride)
					metaInfo.Count = data.Count();
				else
					metaInfo.Count += data.Count();

				var stream = new MemoryStream();
				metaInfo.Write(stream);

				if (!isOverride && dayData != null)
					stream.WriteRaw(dayData);

				stream.WriteRaw(newDayData);

				drive.Save(date, stream.To<byte[]>());
			}
		}

		protected virtual IEnumerable<TData> FilterNewData(IEnumerable<TData> data, TMetaInfo metaInfo)
		{
			var pt = metaInfo.LastTime;
			return data.Where(i => GetTruncatedTime(i) > pt);
		}

		protected virtual bool NeedUpgrade(TMetaInfo metaInfo)
		{
			if (metaInfo == null)
				throw new ArgumentNullException("metaInfo");

			return metaInfo.Version != Version;
		}

		void IMarketDataStorage.Save(IEnumerable data)
		{
			Save((IEnumerable<TData>)data);
		}

		void IMarketDataStorage.Delete(IEnumerable data)
		{
			Delete((IEnumerable<TData>)data);
		}

		public void Delete(IEnumerable<TData> data)
		{
			if (data == null)
				throw new ArgumentNullException("data");

			foreach (var group in data.GroupBy(i => _getTime(i).Date))
			{
				var date = group.Key;

				TMetaInfo metaInfo;
				byte[] dayData;

				if (!GetData(date, out metaInfo, out dayData))
					continue;

				var count = metaInfo.Count;

				if (count != group.Count())
				{
					var loadedData = Serializer.Deserialize(dayData, date, metaInfo).ToDictionary(_getId, i => i);

					foreach (var item in group)
						loadedData.Remove(_getId(item));

					Save(date, loadedData.Values.ToArray(), true);
				}
				else
					Save(date, new TData[0], true);
			}
		}

		public IEnumerableEx<TData> Load(DateTime date)
		{
			TMetaInfo metaInfo;
			byte[] data;

			return !GetData(date, out metaInfo, out data) ? Enumerable.Empty<TData>().ToEx() : Serializer.Deserialize(data, date, metaInfo);
		}

		IMarketDataStorageDayMetaInfo IMarketDataStorage.GetMetaInfo(DateTime date)
		{
			TMetaInfo metaInfo;
			byte[] data;
			GetData(date, out metaInfo, out data);

			return metaInfo;
		}

		private bool GetData(DateTime date, out TMetaInfo metaInfo, out byte[] data)
		{
			var rawData = this.GetStorageDrive().Load(date).To<Stream>();

			if (rawData == null)
			{
				metaInfo = null;
				data = null;
				return false;
			}

			metaInfo = CreateMetaInfo(date);
			metaInfo.Read(rawData);

			data = RemoveMetaInfoHeader(rawData);
			return true;
		}

		protected virtual TMetaInfo CreateMetaInfo(DateTime date)
		{
			return typeof(TMetaInfo).CreateInstance<TMetaInfo>(date);
		}

		private static byte[] RemoveMetaInfoHeader(Stream rawData)
		{
			var body = new byte[rawData.Length - rawData.Position];
			Array.Copy(((MemoryStream)rawData).GetBuffer(), rawData.Position, body, 0, body.Length);
			return body;
		}

		void IMarketDataStorage.Delete(DateTime date)
		{
			Save(date, new TData[0], true);
		}

		IEnumerable IMarketDataStorage.Load(DateTime date)
		{
			return Load(date);
		}

		Func<TData, DateTime> IMarketDataStorageInfo<TData>.GetTime
		{
			get { return _getTime; }
		}
	}
}